package org.zoomdev.zoom.web.modules;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.Part;
import org.zoomdev.zoom.aop.annotations.NoEnhance;
import org.zoomdev.zoom.common.caster.*;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.filter.MethodFilter;
import org.zoomdev.zoom.common.filter.impl.AnnotationMethodFilter;
import org.zoomdev.zoom.common.io.Io;
import org.zoomdev.zoom.common.utils.CachedClasses;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.ioc.Ioc;
import org.zoomdev.zoom.ioc.IocTypeKey;
import org.zoomdev.zoom.ioc.annotations.Inject;
import org.zoomdev.zoom.ioc.annotations.IocBean;
import org.zoomdev.zoom.ioc.annotations.Module;
import org.zoomdev.zoom.ioc.annotations.ModuleInit;
import org.zoomdev.zoom.ioc.impl.builder.IocCreateParameters;
import org.zoomdev.zoom.web.ExceptionHandler;
import org.zoomdev.zoom.web.action.ActionInterceptorFactory;
import org.zoomdev.zoom.web.action.impl.ActionInterceptorFactoryImpl;
import org.zoomdev.zoom.web.action.impl.MethodActionBuilder;
import org.zoomdev.zoom.web.annotations.JsonResponse;
import org.zoomdev.zoom.web.annotations.StringResponse;
import org.zoomdev.zoom.web.handlers.DefaultExceptionHandler;
import org.zoomdev.zoom.web.handlers.NotFoundFilterHandler;
import org.zoomdev.zoom.web.parameter.*;
import org.zoomdev.zoom.web.parameter.parser.impl.UploadParameterAdapterFactory;
import org.zoomdev.zoom.web.rendering.RenderingFactory;
import org.zoomdev.zoom.web.rendering.TemplateRenderingManager;
import org.zoomdev.zoom.web.rendering.impl.*;
import org.zoomdev.zoom.web.router.Router;
import org.zoomdev.zoom.web.view.Redirect;
import org.zoomdev.zoom.web.view.View;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;


/**
 * 查找controller并且映射到router上面
 */
@Module
@NoEnhance
public final class CoreWebModule {

    public static final String NAME = "WEB_MODULE";

    public static boolean showControllerLogs = true;
    private final ParameterAdapter PAGE_PARAMETER_ADAPTER = (context, name, type) -> {
        PreParameter map = context.getPreParameter();
        int value = Caster.to(map.get(name), int.class);
        if (value < 1) {
            value = 1;
        }
        return value;
    };
    @Inject(config = "zoom.validate.minPageSize")
    private int minPageSize = 1;
    @Inject(config = "zoom.validate.maxPageSize")
    private int maxPageSize = 100;
    private final ParameterAdapter PAGE_SIZE_ADAPTER = (context, name, type) -> {
        PreParameter map = context.getPreParameter();
        int value = Caster.to(map.get(name), int.class);
        if (value < minPageSize) {
            value = minPageSize;
        }
        if (value > maxPageSize) {
            value = maxPageSize;
        }
        return value;
    };
    @Inject(config = "zoom.validate.enablePageValidate")
    private boolean enablePageValidate = true;
    @Inject(config = "zoom.web.uploadPath")
    private String uploadPath;

    public static Map<String, Object> getParameters(HttpServletRequest request, Map<String, Object> data) {
        Map<String, String[]> params = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : params.entrySet()) {
            data.put(entry.getKey(), entry.getValue()[0]);
        }
        return data;
    }

    static final class SomeToByteArray implements ValueCaster {

        @Override
        public Object to(Object src) {
            if (src == null) {
                return null;
            }
            if (src instanceof String) {
                return Base64.decodeFast((String) src);
            }
            if (src instanceof Part) {
                try {
                    return Io.readBytes(((Part) src).getInputStream());
                } catch (IOException e) {
                    throw new CasterException("读取上传文件失败", e);
                }
            }
            throw new CasterException("不能支持的类型" + src);
        }
    }

    static class Request2MapProvider extends CasterProviderWithClass {
        @Override
        protected ValueCaster getCaster(Class<?> srcType, Class<?> toType) {
            if (HttpServletRequest.class.isAssignableFrom(srcType) && Map.class.isAssignableFrom(toType)) {
                return new Request2Map(toType);
            }
            return null;
        }
    }

    static class Request2BeanProvider extends CasterProviderWithClass {

        @Override
        public ValueCaster getCaster(Class<?> srcType, Class<?> toType) {
            if (!HttpServletRequest.class.isAssignableFrom(srcType)) {
                return null;
            }
            if (Classes.isSimple(toType)) {
                //转化简单类型应该是不行的
                return null;
            }
            //java开头的一律略过
            if (toType.getName().startsWith("java"))
                return null;


            return new Request2Bean(toType);
        }

    }

    static class Request2Map implements ValueCaster {
        private final Class<?> toType;

        public Request2Map(Class<?> toType) {
            this.toType = toType;
        }

        @Override
        public Object to(Object src) {
            //newInstance
            Map data;
            if (toType == Map.class) {
                data = new HashMap();
            } else {
                data = (Map) Classes.newInstance(toType);
            }

            HttpServletRequest request = (HttpServletRequest) src;
            return getParameters(request, data);
        }
    }

    static class Request2Bean implements ValueCaster {

        private final Class<?> toType;

        public Request2Bean(Class<?> toType) {
            this.toType = toType;
        }

        @Override
        public Object to(Object src) {
            HttpServletRequest request = (HttpServletRequest) src;

            try {
                Object data = toType.newInstance();
                Field[] fields = CachedClasses.getFields(toType);
                for (Field field : fields) {
                    Object value;
                    if (field.getType().isArray()
                            || Iterable.class.isAssignableFrom(field.getType())) {
                        value = request.getParameterValues(field.getName());
                    } else {
                        value = request.getParameter(field.getName());
                    }
                    if (value == null) {
                        continue;
                    }
                    field.set(data, Caster.toType(value, field.getGenericType()));
                }
                return data;
            } catch (Exception e) {
                throw new ZoomException(e);
            }
        }

    }

    class ReturnViewFilter implements MethodFilter {

        @Override
        public boolean accept(Class<?> clazz, Method method) {
            return View.class.isAssignableFrom(method.getReturnType());
        }
    }

    class ReturnRedirectFilter implements MethodFilter {

        @Override
        public boolean accept(Class<?> clazz, Method method) {
            return Redirect.class.isAssignableFrom(method.getReturnType());
        }
    }

    @ModuleInit
    public void configPage(ParameterAdapterFactoryManager manager) {
        if (!enablePageValidate) {
            return;
        }
        manager.add((name, type, annotations) -> {
            if ("page".equals(name) && (type == int.class || type == Integer.class)) {
                return PAGE_PARAMETER_ADAPTER;
            }
            if ("pageSize".equals(name) && (type == int.class || type == Integer.class)) {
                return PAGE_SIZE_ADAPTER;
            }
            return null;
        });
    }

    public void setMinPageSize(int minPageSize) {
        this.minPageSize = minPageSize;
    }

    public void setEnablePageValidate(boolean enablePageValidate) {
        this.enablePageValidate = enablePageValidate;
    }

    @IocBean
    public NotFoundFilterHandler getNotFound() {
        return new NotFoundFilterHandler();
    }

    @IocBean
    public ExceptionHandler getExceptionHandler() {
        return new DefaultExceptionHandler();
    }

    /**
     * 解析controller
     */
    @ModuleInit(after = true)
    public void resolveControllers(MethodActionBuilder builder, Ioc ioc,
                                   @Inject(config = "zoom.scan.controllers")
                                   String scanFilter) {
        IocCreateParameters parameters = ioc.get(new IocTypeKey(IocCreateParameters.class));
        if (parameters != null) {
            scanFilter = parameters.get("zoom.scan.controllers");
        }

        builder.setScanFilter(scanFilter);
        builder.build();
    }

    @ModuleInit
    public void configCaster() {
        Caster.registerCastProvider(new Request2MapProvider());
        Caster.registerCastProvider(new Request2BeanProvider());
    }

    @ModuleInit
    public void configFastCaster() {
        try {
            FastToBeanCaster.register(byte[].class, new SomeToByteArray());
        } catch (Throwable e) {
        }
    }

    @IocBean
    public Router getRouter() {
        return new Router();
    }

    @IocBean
    public TemplateRenderingManager getTemplateRenderingManager() {
        TemplateRenderingManager manager = new TemplateRenderingManager();
        manager.add("html", new HtmlRendering());
        return manager;
    }

    @IocBean
    public ActionInterceptorFactory getActionInterceptorFactory() {
        return new ActionInterceptorFactoryImpl();
    }

    @IocBean
    public RenderingFactory getRenderingFactory(TemplateRenderingManager manager) {
        RenderingFactory factory = new RenderingFactory();
        factory.add(new ViewRendering(), new ReturnViewFilter());
        factory.add(new JsonRendering(), new AnnotationMethodFilter(JsonResponse.class));
        factory.add(new StringRendering(), new AnnotationMethodFilter(StringResponse.class));
        factory.add(new RedirectRendering(), new ReturnRedirectFilter());
        factory.add(new TemplateRendering(manager));
        return factory;
    }

    @IocBean
    public ParameterAdapterFactoryManager getParameterAdapterFactoryManager() {
        ParameterAdapterFactoryManager manager = new ParameterAdapterFactoryManager();
        try {
            Class.forName("org.apache.commons.fileupload.FileItem", false, getClass().getClassLoader());
            manager.insert(new UploadParameterAdapterFactory(), 0);
        } catch (Exception e) {

        }

        return manager;
    }

    @IocBean
    public ParameterParserFactory getParameterParserFactory(ParameterAdapterFactoryManager manager) {
        return new ParameterParserFactory(manager);
    }

    @IocBean
    public PreParameterParserManager getPreParameterParserManager() {
        return new PreParameterParserManager(uploadPath);
    }
}
